package com.qqt.csr.file.controller;

import cn.hutool.core.io.FileUtil;
import com.obs.services.exception.ObsException;
import com.qqt.csr.common.enums.FileGroupEnum;
import com.qqt.csr.common.enums.FileTypeEnum;
import com.qqt.csr.common.exception.BaseException;
import com.qqt.csr.common.exception.BizException;
import com.qqt.csr.common.exception.StatusCode;
import com.qqt.csr.common.idempotent.Idempotent;
import com.qqt.csr.common.interceptor.auth.Authorize;
import com.qqt.csr.common.log.Log;
import com.qqt.csr.common.utils.ServiceAssert;
import com.qqt.csr.common.vo.resp.ObjectResponse;
import com.qqt.csr.file.service.IMultipartFileService;
import com.qqt.csr.file.service.ISingleFileService;
import com.qqt.csr.file.vo.req.AbortMultiUploadReq;
import com.qqt.csr.file.vo.req.InitMultiUploadReq;
import com.qqt.csr.file.vo.req.MergeMultiUploadReq;
import com.qqt.csr.file.vo.req.UploadPartReq;
import com.qqt.csr.file.vo.resp.FileUploadInfoResp;
import com.qqt.csr.file.vo.resp.InitMultiUploadResp;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import java.io.*;
import java.util.function.Supplier;

@Slf4j
@RestController
@RequestMapping("oss/file")
@Tag(name = "oss文件上传", description = "oss文件上传")
public class OssFileUploadController {
    @Value("${file.upload.bucketName}")
    private String bucketName;
    @Value("${file.upload.img.maxSize}")
    private Long uploadImgMaxSize;
    @Value("${file.upload.doc.maxSize}")
    private Long uploadDocMaxSize;

    @Autowired
    private IMultipartFileService multipartFileService;
    @Autowired
    private ISingleFileService singleFileService;

    /**
     * 初始化分段任务
     *
     * @param req
     * @return
     */
    @Authorize
    @Operation(summary = "初始化分段任务")
    @PostMapping("/multi/init")
    @Log(desc = "初始化分段任务")
    @Idempotent(model = "oss:file:multi:init:")
    public ObjectResponse<InitMultiUploadResp> initiateMultipartUpload(@Validated @RequestBody InitMultiUploadReq req) throws IOException {
        ServiceAssert.notNull(req, StatusCode.Common.NULL_ERROR.getCode(), "请求参数不能为空");
        ServiceAssert.isTrue(StringUtils.isNotBlank(req.getFileName()), StatusCode.Common.NULL_ERROR.getCode(), "文件名不能未空");
        String extension = FileUtil.getSuffix(req.getFileName());
        ServiceAssert.isTrue(StringUtils.isNotBlank(extension), StatusCode.Common.NULL_ERROR.getCode(), "不支持当前文件类型");

        FileTypeEnum fileType = FileTypeEnum.of(extension);
        ServiceAssert.notNull(fileType, StatusCode.Common.ILLEGAL.getCode(), "不支持当前文件类型");

        if (FileGroupEnum.IMG.equals(fileType.getGroup())) {
            ServiceAssert.isTrue(req.getFileSize() <= uploadImgMaxSize, StatusCode.Common.ILLEGAL.getCode(), "图片大小不能超过50M");
        } else if (FileGroupEnum.DOC.equals(fileType.getGroup())) {
            ServiceAssert.isTrue(req.getFileSize() <= uploadDocMaxSize, StatusCode.Common.ILLEGAL.getCode(), "文件大小不能超过100M");
        } else {
            throw new BizException(StatusCode.Common.ILLEGAL.getCode(), "不支持当前文件类型");
        }

        req.setFileTypeEnum(fileType);
        String uploadId = handleUploadException(() -> multipartFileService.initiateMultipartUpload(bucketName, req));
        return ObjectResponse.success(InitMultiUploadResp.builder()
                .uploadId(uploadId)
                .build());
    }

    @Log(desc = "上传分片文件", outRespParam = false)
    @Operation(summary = "分段上传", parameters = {
            @Parameter(name = "uploadId", description = "分段上传id", required = true, schema = @Schema(type = "string")),
            @Parameter(name = "fileName", description = "原始文件名", required = true, schema = @Schema(type = "string")),
            @Parameter(name = "chunkIndex", description = "分段编码，取值范围：1-10000", required = true, schema = @Schema(type = "integer")),
            @Parameter(name = "file", description = "分片文件", required = true, schema = @Schema(format = "binary"))
    })
    @PostMapping(value = "/multi/uploadPart")
    public ObjectResponse<String> uploadPart(@RequestParam("uploadId") String uploadId,
                                             @RequestParam("fileName") String fileName,
                                             @RequestParam("chunkIndex") Integer chunkIndex,
                                             @RequestParam("file") MultipartFile file) throws IOException {
        UploadPartReq req = UploadPartReq.builder().uploadId(uploadId).fileName(fileName).chunkIndex(chunkIndex).build();
        ServiceAssert.notNull(req, StatusCode.Common.NULL_ERROR.getCode(), "请求参数不能为空");
        ServiceAssert.isTrue(StringUtils.isNotBlank(req.getUploadId()), StatusCode.Common.NULL_ERROR.getCode(), "分段上传id不能为空");
        ServiceAssert.isTrue(req.getChunkIndex() > 0, StatusCode.Common.NULL_ERROR.getCode(), "分段编码不能小于1");
        ServiceAssert.isTrue(req.getChunkIndex() <= 10000, StatusCode.Common.NULL_ERROR.getCode(), "分段编码不能小于1");
        ServiceAssert.isTrue(StringUtils.isNotBlank(req.getFileName()), StatusCode.Common.NULL_ERROR.getCode(), "分段编码不能大于10000");
        ServiceAssert.isTrue(file.getSize() > 0, StatusCode.Common.NULL_ERROR.getCode(), "上传文件不能为空");
        InputStream fileInputStream = file.getInputStream();
        handleUploadException(() -> {
            multipartFileService.uploadPart(bucketName, req, fileInputStream);
        });
        return ObjectResponse.success(null);
    }

    @Operation(summary = "合并所有分段")
    @PostMapping("/multi/merge")
    @Log(desc = "合并所有分段")
    @Idempotent(model = "oss:file:multi:merge:")
    public ObjectResponse<FileUploadInfoResp> completeMultipartUpload(@Validated @RequestBody MergeMultiUploadReq req) {
        ServiceAssert.notNull(req, StatusCode.Common.NULL_ERROR.getCode(), "请求参数不能为空");
        String fileUrl = handleUploadException(() -> multipartFileService.completeMultipartUpload(bucketName, req.getFileName(), req.getUploadId()));
        return ObjectResponse.success(FileUploadInfoResp.builder().url(fileUrl).build());
    }

    @Authorize
    @Operation(summary = "取消分段上传任务")
    @PostMapping("/multi/abort")
    @Log(desc = "取消分段上传任务")
    @Idempotent(model = "oss:file:multi:abort:")
    public ObjectResponse<String> abortMultipartUpload(@Validated @RequestBody AbortMultiUploadReq req) {
        ServiceAssert.notNull(req, StatusCode.Common.NULL_ERROR.getCode(), "请求参数不能为空");
        handleUploadException(() -> {
            multipartFileService.abortMultipartUpload(bucketName, req.getFileName(), req.getUploadId());
        });
        return ObjectResponse.success(null);
    }

    @Authorize
    @Operation(summary = "上传3M以内的单个小文件")
    @PostMapping("/upload")
    @Log(desc = "上传3M以内的单个小文件")
    public ObjectResponse<FileUploadInfoResp> upload(@RequestParam("file") MultipartFile file) throws IOException {
        ServiceAssert.isTrue(StringUtils.isNotBlank(file.getOriginalFilename()), StatusCode.Common.NULL_ERROR.getCode(), "文件名不能未空");
        String extension = FileUtil.getSuffix(file.getOriginalFilename());
        ServiceAssert.isTrue(StringUtils.isNotBlank(extension), StatusCode.Common.NULL_ERROR.getCode(), "不支持当前文件类型");

        FileTypeEnum fileType = FileTypeEnum.of(extension);
        ServiceAssert.notNull(fileType, StatusCode.Common.ILLEGAL.getCode(), "不支持当前文件类型");

        long fileSize = file.getSize();
        ServiceAssert.isTrue(fileSize <= 3 * 1024 * 1024, StatusCode.Common.ILLEGAL.getCode(), "文件大小不能超过3M");

        InputStream fileInputStream = file.getInputStream();
        FileUploadInfoResp resp = handleUploadException(() -> singleFileService.uploadFile(bucketName, file.getOriginalFilename(), fileType, fileInputStream));
        return ObjectResponse.success(resp);
    }

    private <T> T handleUploadException(Supplier<T> supplier) {
        try {
            T result = supplier.get();
            return result;
        } catch (BaseException e) {
            throw e;
        } catch (ObsException e) {
            log.error("请求oss接口异常!errorCode:{},errorMsg:{}", e.getErrorCode(), e.getErrorMessage(), e);
            throw new BaseException(StatusCode.Common.THIRD_API_ERROR.getCode(), e.getErrorMessage());
        } catch (Exception e) {
            log.error("系统异常!", e);
            throw e;
        }
    }

    private void handleUploadException(Runnable runnable) {
        handleUploadException(() -> {
            runnable.run();
            return null;
        });
    }
}
